package prefuse.data.query;

import java.util.ArrayList;

import javax.swing.DefaultListSelectionModel;
import javax.swing.MutableComboBoxModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

import prefuse.util.collections.CopyOnWriteArrayList;

/**
 * List data model supporting both data modeling and selection management.
 * Though generally useful, this has been designed particularly to support
 * dynamic queries.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class ListModel extends DefaultListSelectionModel implements MutableComboBoxModel {
    private ArrayList m_items = new ArrayList();
    private CopyOnWriteArrayList m_lstnrs = new CopyOnWriteArrayList();

    /**
     * Create an empty ListModel.
     */
    public ListModel() {
        // do nothing
    }

    /**
     * Create a ListModel with the provided items.
     * @param items the items for the data model.
     */
    public ListModel(final Object[] items) {
        for (int i = 0; i < items.length; ++i)
            m_items.add(items[i]);
    }

    // --------------------------------------------------------------------

    /**
     * Indicates if the ListModel currently has multiple selections.
     * @return true if there are multiple selections, false otherwise
     */
    private boolean isMultipleSelection() {
        return getMaxSelectionIndex() - getMinSelectionIndex() > 0;
    }

    /**
     * @see javax.swing.ComboBoxModel#getSelectedItem()
     */
    public Object getSelectedItem() {
        int idx = getMinSelectionIndex();
        return (idx == -1 ? null : m_items.get(idx));
    }

    /**
     * @see javax.swing.ComboBoxModel#setSelectedItem(java.lang.Object)
     */
    public void setSelectedItem(Object item) {
        int idx = m_items.indexOf(item);
        if (idx < 0)
            return;

        if (!isMultipleSelection() && idx == getMinSelectionIndex())
            return;

        super.setSelectionInterval(idx, idx);
        fireDataEvent(this, ListDataEvent.CONTENTS_CHANGED, -1, -1);
    }

    /**
     * @see javax.swing.ListModel#getSize()
     */
    public int getSize() {
        return m_items.size();
    }

    /**
     * @see javax.swing.ListModel#getElementAt(int)
     */
    public Object getElementAt(int idx) {
        return m_items.get(idx);
    }

    /**
     * @see javax.swing.MutableComboBoxModel#addElement(java.lang.Object)
     */
    public void addElement(Object item) {
        m_items.add(item);
        int sz = m_items.size() - 1;
        fireDataEvent(this, ListDataEvent.INTERVAL_ADDED, sz, sz);
        if (sz >= 0 && isSelectionEmpty() && item != null)
            setSelectedItem(item);
    }

    /**
     * @see javax.swing.MutableComboBoxModel#insertElementAt(java.lang.Object, int)
     */
    public void insertElementAt(Object item, int idx) {
        m_items.add(idx, item);
        fireDataEvent(this, ListDataEvent.INTERVAL_ADDED, idx, idx);
    }

    /**
     * @see javax.swing.MutableComboBoxModel#removeElement(java.lang.Object)
     */
    public void removeElement(Object item) {
        int idx = m_items.indexOf(item);
        if (idx >= 0)
            removeElementAt(idx);
    }

    /**
     * @see javax.swing.MutableComboBoxModel#removeElementAt(int)
     */
    public void removeElementAt(int idx) {
        if (!isMultipleSelection() && idx == getMinSelectionIndex()) {
            int sidx = (idx == 0 ? getSize() == 1 ? -1 : idx + 1 : idx - 1);
            Object sel = (sidx == -1 ? null : m_items.get(sidx));
            setSelectedItem(sel);
        }

        m_items.remove(idx);
        fireDataEvent(this, ListDataEvent.INTERVAL_REMOVED, idx, idx);
    }

    // --------------------------------------------------------------------
    // List Data Listeners

    /**
     * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
     */
    public void addListDataListener(ListDataListener l) {
        if (!m_lstnrs.contains(l))
            m_lstnrs.add(l);
    }

    /**
     * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
     */
    public void removeListDataListener(ListDataListener l) {
        m_lstnrs.remove(l);
    }

    /**
     * Fires a change notification in response to changes in the ListModel.
     */
    protected void fireDataEvent(Object src, int type, int idx0, int idx1) {
        Object[] lstnrs = m_lstnrs.getArray();
        if (lstnrs.length > 0) {
            ListDataEvent e = new ListDataEvent(src, type, idx0, idx1);
            for (int i = 0; i < lstnrs.length; ++i) {
                ((ListDataListener) lstnrs[i]).contentsChanged(e);
            }
        }
    }

} // end of class ListModel
